﻿#include <iostream>
#include <string>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

#include "classes/MMacros.h"
#include "classes/MVideoManager.hpp"
#include "classes/MDataManager.hpp"
#include "classes/MSingleTask.hpp"
#include "classes/testsuite.h"
using namespace std;
using namespace cv;
const string version = "5.1";

/*	<description>
		parse option configuration through standard-input
		output parameters are saved in lParam
	</description>
	<param>
		argc, argv: standard input
		mode: configuration only or tracing only
		rootpath: parent path containing task list and multiple video subdirectories
		return: failture (wrong usage)
		        manual (configure through interactive QA)
				cmdline (configure through direct cmdline pipeline) 
	</param>
*/
string parse_opt(int argc, char** argv, string& mode, string& rootpath, vector<int>& lParam) {
	string ret = "";
	if (argc <= 4) {
		cout << "Program version: " << version << endl;
		cout << "<Usage> ftrace " << " --mode <configure/tracing> --rootpath <path_to_your_videos>" << endl;
		cout << "\t--mode"
			<< "\t\t<configure> if set as configure, program will only configure tracing parameters;\n"
			<< "\t\t\t<tracing> if set as tracing, program will trace video w/o configuration;\n"
			<< "\t\t\t(if video is not configured previously, then tracing will fail)." << endl;
		cout << "\t--rootpath"
			<< "\tpath to your video clips,\n"
			<< "\t\t\twhere clips are already moved to different sub-directory,\n"
			<< "\t\t\taccording to their channel indices." << endl;
		cout << "Now switched to setting-wizard.\n";
		cout << "task working directory: ";
		char bufRootpath[2048];
		cin.getline(bufRootpath, 2048);
		rootpath = bufRootpath;
		cout << "configure or tracing?\n\t0=configure;\n\t1=tracing\n\tYour Choice = ";
		int iChoice = 0; cin >> iChoice;
		if (iChoice == 0) mode = "configure";
		else mode = "tracing";
		ret = "manual";
	}
	else {
		cout << "CLI mode on: lParams should be passed in.\n";
		string slOpt = "";
		for (int i = 1; i < argc; i++) {
			slOpt += " ";
			slOpt += argv[i];
		}
		vector<string> lOpt = toolstring::split(slOpt, "--");
		if (lOpt.size() < 4) {
			cout << "commandline syntax error!" << endl;
			return "failure";
		}
		for (int j = 1; j < (int)lOpt.size(); j++) {
			vector<string> lTerm = toolstring::split(lOpt[j], ' ');
			for (int k = 0; k < (int)lTerm.size(); k++) {
				toolstring::rstrip(lTerm[k]);
				toolstring::lstrip(lTerm[k]);
			}
			if (lTerm[0] == "mode") mode = lTerm[1];
			else if (lTerm[0] == "rootpath") rootpath = lTerm[1];
			else if (lTerm[0] == "param") {
				if (mode == "configure") {
					lParam.push_back(atoi(lTerm[1].c_str()));
					lParam.push_back(atoi(lTerm[2].c_str()));
				}
				else if (mode == "tracing") {
					lParam.push_back(atoi(lTerm[1].c_str()));
					lParam.push_back(atoi(lTerm[2].c_str()));
					lParam.push_back(atoi(lTerm[3].c_str()));
					lParam.push_back(atoi(lTerm[4].c_str()));
				}

			}

		}
		ret = "cmdline";
	}
	return ret;
}

/*	<description>
		fully-connected network with two hidden layers
	</description>
	<param>
		Mu, Sigma are mean and std of 8-bit intensity in trainset images
		Theta1, Theta2 are weights of fc-1 and fc-2 (including biases)
			# the weights are trained on MATLAB scripts (see another project for details)
		return 1d coordinates of glass tube bbox in a larger ROI
	</param>
*/
vector<Rect> semiautodetect(Mat& imgBgGr) {
	MPredictor* pPred = new MPredictor();
	pPred->loadModel("Mu.txt", "Sigma.txt", "Theta1.txt", "Theta2.txt");
	return pPred->predictMultipleLine(imgBgGr);
}

/*	<description>
		process tracing task for one directory
	</description>
	<param>
		rootpath: directory containing video clips
		return: -1 means no video clips;
	</param>
*/
int config_single_task(string rootpath) {
	// # get clips list
	if (rootpath.size() == 0) rootpath = "./";
	string pathAvi = toolpath::joinPath(rootpath, "*.avi");
	vector<string> lClips = toolpath::getFiles(pathAvi);
	if (lClips.size() == 0) {  // warning: no clips found
		cout << "No video clips in " << rootpath << endl;
		return -1;
	}

	// # output clip list in rootpath
	string pathLsAvi = toolpath::joinPath(rootpath, "list.txt");
	ofstream hLsAvi(pathLsAvi);
	for (int i = 0; i < (int)lClips.size(); i++) {
		hLsAvi << lClips[i] << endl;
	} hLsAvi.close();

	// # init data manager
	MDataManager* pData = new MDataManager;
	pData->initRootPath(rootpath.c_str());
	pData->initIOFiles();
	pData->resetIOFileStream();

	// # init a video group
	MVideoManager* pVid = new MVideoManager(rootpath.c_str(), 1);
	//pVid->writeData(pData);
	pVid->exportConfig(pData);

	// # init dynamic background
	MDynamicGrBgManager* pBg = new MDynamicGrBgManager();
	pBg->initWith(pVid);
	pBg->exportConfig(pData);

	// # init Rect roi
	MRectRoiManager* pRoi = new MRectRoiManager();
	Mat imgFrTmp; pVid->readPerCall(imgFrTmp);
	pVid->resetTimer();
	if (!pRoi->importConfig(pData))
		pRoi->initAutoRoiSeriesWith(imgFrTmp);
	pRoi->exportConfig(pData, imgFrTmp);

	// # init light monitor
	MLightMonitor* pLight = new MLightMonitor();
	pLight->importConfig(pData);
	pLight->setReference(imgFrTmp);
	pLight->exportConfig(pData);

	// # init length ratio
	MTracerManager* pTracers = new MTracerManager();
	pTracers->importConfig(pData);
	pTracers->setTracerWith(pRoi, imgFrTmp, pBg->getBg());
	pTracers->exportConfig(pData);

	// export configuration data
	pBg->clear();
	pData->closeIOFiles();
	return 0;
}

/*	<description>
		same as config_single_task, with only one difference:
		it starts configuration at specific video clip and frame as denoted
	</description>
	<param>
		iClip, iFr are indicated indices of specific video clip and in-clip frame
		return -1 means invalid input rootpath
		return 0 means success
	</param>
*/
int config_single_task_with(string rootpath, int iClip, int iFr) {
	// # get ordered list of video clips
	if (rootpath.size() == 0) rootpath = "./";
	vector<string> lClips = toolpath::getFiles(rootpath, "*.avi");
	if (lClips.size() == 0) {  // warning: no clips found
		cout << "No video clips in " << rootpath << endl;
		return -1;
	}

	// # output clip list in rootpath
	string pathLsAvi = toolpath::joinPath(rootpath, "list.txt");
	ofstream hLsAvi(pathLsAvi);
	for (int i = 0; i < (int)lClips.size(); i++) {
		hLsAvi << lClips[i] << endl;
	} hLsAvi.close();

	// # init data manager
	MDataManager* pData = new MDataManager;
	pData->initRootPath(rootpath.c_str());
	pData->initIOFiles();
	pData->resetIOFileStream();

	// # init a video group
	MVideoManager* pVid = new MVideoManager(rootpath.c_str(), 1);
	//pVid->writeData(pData);
	pVid->exportConfig(pData);

	// # init dynamic background
	MDynamicGrBgManager* pBg = new MDynamicGrBgManager();
	pBg->initWith(pVid);
	pBg->exportConfig(pData);

	// # init Rect roi
	MRectRoiManager* pRoi = new MRectRoiManager();
	pVid->resetTimer(iClip, iFr);
	Mat imgFrTmp; pVid->readPerCall(imgFrTmp);
	if (!pRoi->importConfig(pData))
		pRoi->initAutoRoiSeriesWith(imgFrTmp);
	pRoi->exportConfig(pData, imgFrTmp);

	// # init light monitor
	MLightMonitor* pLight = new MLightMonitor();
	pLight->importConfig(pData);
	pLight->setReference(imgFrTmp);
	pLight->exportConfig(pData);

	// # init length ratio
	MTracerManager* pTracers = new MTracerManager();
	pTracers->importConfig(pData);
	pTracers->setTracerWith(pRoi, imgFrTmp, pBg->getBg());
	pTracers->exportConfig(pData);

	// export configuration data
	pBg->clear();
	pData->closeIOFiles();
	return 0;
}

/*	<description>
		process tracing for one directory,
		rootpath should include all video clips for one channel,
		name of video clip is predefined by aviconverter, with format like:
		chn-%Y-%m-%d-%H.%M.%S-%H.%M.%S.avi
		duration is one hour for each video clip
	</description>
	<param>
		isShowGraph: is set when tracing preview is wanted
		return: 0 means finished
	</param>
*/
int trace_single_task(string rootpath, bool isShowGraph = true) {
	// # init tracing task
	MSingleTask* pTask = new MSingleTask;
	cout << "How many frames to trace per second? cps = ";
	int cps = 1; cin >> cps;
	cout << "Start tracing at (-1 or 0 means start from the 1st clip) #Clip = ";
	int iStartClip = -1; cin >> iStartClip;
	cout << "Start tracing at (-1 or 0 means start from the 1st frame) #Fr = ";
	int iStartFr = -1; cin >> iStartFr;
	cout << "Stop tracing at (-1 means stop at the last clip) #Clip = ";
	int iStopClip = -1; cin >> iStopClip;
	cout << "Stop tracing at (-1 means stop at the last fr) #Fr = ";
	int iStopFr = -1; cin >> iStopFr;
	pTask->initWith(rootpath, cps, false, iStartClip, iStartFr, iStopClip, iStopFr);

	// # video loop
	namedWindow(wTest, CV_WINDOW_NORMAL);
	Mat imgLabel;
	while (1) {
		if (pTask->computeNextFrame()) { // trace current frame
			if (isShowGraph) { // optional, online preview of tracing image
				pTask->labelWith(imgLabel);
				imshow(wTest, imgLabel);
			}
			pTask->reportProgress(); // log tracing status, and record tracing data
			pTask->writeData();
		}
		else { // reach the end, stop tracing
			break;
		}
		if (isShowGraph) { // optional, use Esc to stop tracing, only work when online preview is on
			int key = waitKey(1);
			if (key == 27) {
				pTask->stop();
			}
		}
	}
	pTask->release();
	destroyWindow(wTest);

	return 0;
}

/*	<description>
		wrapper function of serial config_single_task for multiple directories
	</description>
	<param>
		workdir is the parent directory containing multiple video subdirectories
		for each subdirectory, do config_single_task
		return 0 means success
	</param>
*/
int config_multitask(string workdir, string fTasklist) {
	string pathTasklist = toolpath::joinPath(workdir, fTasklist);
	ifstream hTasklist(pathTasklist);
	string line;
	while (getline(hTasklist, line)) {
		cout << "\tConfigure #task [" << line << "]\n";
		config_single_task(line);
	}
	hTasklist.close();
	return 0;
}

/*	<description>
		same as config_multitask, with one difference:
		it selects frame to do configuration as indicated, not the 1st frame
	</description>
	<param>
		fTasklist: a file containing ordered list of absolute paths for video subdirectories
		iClip, iFr are indicated indices of specific video clip and in-clip frame
		return -1 means invalid input rootpath
		return 0 means success
	</param>
*/
int config_multitask_with(string workdir, string fTasklist, int iClip, int iFr) {
	string pathTasklist = toolpath::joinPath(workdir, fTasklist);
	ifstream hTasklist(pathTasklist);
	string line;
	while (getline(hTasklist, line)) {
		cout << "\tConfigure #task [" << line << "]\n";
		config_single_task_with(line, iClip, iFr);
	}
	hTasklist.close();
	return 0;
}

/*	<description>
		wrapper function of serial trace_single_task for multiple video subdireactories
		tracing start at the very beginning for all subdirectory,
		please ensure the video recordings start at the same time
	</description>
	<param>
		wordir: the parent path containing multiple video subdirectories
		fTasklist: a file containing ordered list of absolute paths for video subdirectories
		isShowGraph: is set when tracing preview is wanted
		return: 0 means finished
	</param>
*/
int trace_multitask(string workdir, string fTasklist, bool isShowGraph = false) {
	// # init multi tracing tasks
	vector<MSingleTask*> lpTasks;
	// cout << "How many frames to trace per second? cps = ";
	int cps = 1;
	// cin >> cps;
	int iStartClip = -1;
	int iStartFr = -1;
	int iStopClip = -1;
	int iStopFr = -1;
	// cout << "Do you want to define the times to start/stop tracing?\n\t0 = no;\n\t1 = yes\n\tYour choice = ";
	int isDefineTime = 0;
	// cin >> isDefineTime;
	if (isDefineTime) {
		cout << "Start tracing at (-1 or 0 means start from the 1st clip) #Clip = ";
		cin >> iStartClip;
		cout << "Start tracing at (-1 or 0 means start from the 1st frame) #Fr = ";
		cin >> iStartFr;
		cout << "Stop tracing at (-1 means stop at the last clip) #Clip = ";
		cin >> iStopClip;
		cout << "Stop tracing at (-1 means stop at the last fr) #Fr = ";
		cin >> iStopFr;
	}
	//cout << "Dow you want to refine ROI settings? (0=no / 1=yes) Your Choice = ";
	int isRefineRoi = 0;
	//cin >> isRefineRoi;

	ifstream hTasklist(toolpath::joinPath(workdir, fTasklist));
	string line;
	MSingleTask* pTmp = NULL;
	vector<string> lsPaths;
	while (getline(hTasklist, line)) {
		cout << "Task #" << line << endl;
		lsPaths.push_back(line);
		pTmp = new MSingleTask;
		pTmp->initWith(line, cps, false, iStartClip, iStartFr, iStopClip, iStopFr, isRefineRoi);
		lpTasks.push_back(pTmp);
	}
	hTasklist.close();

	// # multi video loop in parallel
	/* process while labeling */
	int nTasks = (int)lpTasks.size();
	Mat* lsImgLabel = new Mat[nTasks];
	if (isShowGraph) {
		for (int i = 0; i < nTasks; i++) {
			namedWindow(lsPaths[i], CV_WINDOW_NORMAL);
		}
	}

	/* multi tracing loop */
	int key = 1;
	int nStop = 0;
	while (1) {
#pragma omp parallel for
		for (int i = 0; i < nTasks; i++) {
			if (lpTasks[i]->computeNextFrame()) {
				if (isShowGraph) {
					lpTasks[i]->labelWith(lsImgLabel[i]);
				}
				lpTasks[i]->writeData();
			}
			else {
				lpTasks[i]->stop();
			}
		}

		for (int i = 0; i < nTasks; i++) {
			if (isShowGraph) {
				imshow(lsPaths[i], lsImgLabel[i]);
			}
			else {
				lpTasks[i]->reportProgress();
			}
		}
		if (isShowGraph) {
			key = waitKey(1);
		}
		nStop = 0;
		for (int i = 0; i < nTasks; i++) {
			if (lpTasks[i]->isStop()) {
				nStop++;
			}
		}
		if (nStop == nTasks) {
			key = 27;
		}
		if (key == 27) {
			for (int i = 0; i < nTasks; i++) {
				lpTasks[i]->stop();
				if (lpTasks[i]->isStop()) {
					cout << "Task #" << i << " has stop." << endl;
				}
			} break;
		}
	}
	for (int i = 0; i < nTasks; i++) {
		lpTasks[i]->release();
		if (isShowGraph) {
			destroyAllWindows();
		}
	}
	return 0;
}

/*	<description>
		wrapper function of serial trace_single_task for multiple video subdireactories
		same as trace_multitask, with one difference:
		tracing start at specific frame in specific clip as indicated
	</description>
	<param>
		return: 0 means finished
	</param>
*/
int trace_multitask_with(string workdir, string fTasklist, bool isShowGraph = false, int iStartClip = -1, int iStartFr = -1, int iStopClip = -1, int iStopFr = -1) {
	// # init multi tracing tasks
	vector<MSingleTask*> lpTasks;
	cout << "How many frames to trace per second? cps = ";
	int cps = 1;
	cin >> cps;
	//want to define the times to start/stop tracing?\n\t0 = no;\n\t1 = yes\n\tYour choice = ";
	// cin >> isDefineTime;
	//cout << "Dow you want to refine ROI settings? (0=no / 1=yes) Your Choice = ";
	int isRefineRoi = 0;
	//cin >> isRefineRoi;

	ifstream hTasklist(toolpath::joinPath(workdir, fTasklist));
	string line;
	MSingleTask* pTmp = NULL;
	vector<string> lsPaths;
	while (getline(hTasklist, line)) {
		cout << "Task # " << line << endl;
		lsPaths.push_back(toolstring::rstrip(line, "\n\r\n\r"));
		pTmp = new MSingleTask;
		pTmp->initWith(line, cps, false, iStartClip, iStartFr, iStopClip, iStopFr, isRefineRoi);
		lpTasks.push_back(pTmp);
	}
	hTasklist.close();

	// # multi video loop in parallel
	/* process while labeling */
	int nTasks = (int)lpTasks.size();
	Mat* lsImgLabel = new Mat[nTasks];
	if (isShowGraph) {
		for (int i = 0; i < nTasks; i++) {
			namedWindow(lsPaths[i], CV_WINDOW_NORMAL);
		}
	}

	/* multi tracing loop */
	int key = 1;
	int nStop = 0;
	while (1) {
#pragma omp parallel for
		for (int i = 0; i < nTasks; i++) {
			if (lpTasks[i]->computeNextFrame()) {
				if (isShowGraph) {
					lpTasks[i]->labelWith(lsImgLabel[i]);
				}
				lpTasks[i]->writeData();
			}
			else {
				lpTasks[i]->stop();
			}
		}

		for (int i = 0; i < nTasks; i++) {
			if (isShowGraph) {
				imshow(lsPaths[i], lsImgLabel[i]);
			}
			else {
				lpTasks[i]->reportProgress();
			}
		}
		if (isShowGraph) {
			key = waitKey(1);
		}
		nStop = 0;
		for (int i = 0; i < nTasks; i++) {
			if (lpTasks[i]->isStop()) {
				nStop++;
			}
		}
		if (nStop == nTasks) {
			key = 27;
		}
		if (key == 27) {
			for (int i = 0; i < nTasks; i++) {
				lpTasks[i]->stop();
				if (lpTasks[i]->isStop()) {
					cout << "Task #" << i << " has stop." << endl;
				}
			} break;
		}
	}
	for (int i = 0; i < nTasks; i++) {
		lpTasks[i]->release();
		if (isShowGraph) {
			destroyAllWindows();
		}
	}
	return 0;
}

/*	<description>
		main entry to single tracer
		wrapping functions for both configuration and tracing parallel (see above for detail)
		mode configure: do configure only, including:
		    # semiauto configuration of ROIs for every glass tubes
			# interactive adjustment of ROIs after semiauto configuration
			# 1d scaling of pixel:mm by interactive labeling
		mode tracing: do tracing w/o auto configuration
		return:see returns at function trace_multitask_with
	</description>
	<param>
		
	</param>
*/
int run_single_tracer(int argc, char** argv) {
	string rootpath = "";
	string mode = "configure";
	vector<int> lParam;
	string settingmode = parse_opt(argc, argv, mode, rootpath, lParam);
	int ret = -1;
	if (mode == "configure") {
		cout << "\tConfigure tracing program:\n";
		if (settingmode == "manual") {
			cout << "configure roi at #clip (-1 means default 0) = ";
			int iClip; cin >> iClip;
			cout << "configure roi at #frame (-1 means default 0) = ";
			int iFr; cin >> iFr;
			ret = config_multitask_with(rootpath, "tasklist.txt", iClip, iFr);
		}
		else {
			ret = config_multitask_with(rootpath, "tasklist.txt", lParam[0], lParam[1]);
		}
	}
	else if (mode == "tracing") {
		//cout << "Show tracing videos?\n\t0 = no;\n\t1 = yes\n\tYour choice = ";
		int isPreview = 0;
		if (settingmode == "manual") {
			cout << "tracing starts from #clip (-1 means default 0) = ";
			int iClip; cin >> iClip;
			cout << "tracing starts from #frame (-1 means default 0) = ";
			int iFr; cin >> iFr;
			cout << "tracing stops at #clip (-1 means default end) = ";
			int iClip_stop; cin >> iClip_stop;
			cout << "tracing stops at #frame (-1 means default end) = ";
			int iFr_stop; cin >> iFr_stop;
			ret = trace_multitask_with(rootpath, "tasklist.txt", isPreview, iClip, iFr, iClip_stop, iFr_stop);
		}
		else
			ret = trace_multitask_with(rootpath, "tasklist.txt", isPreview, lParam[0], lParam[1], lParam[2], lParam[3]);
	}
	return ret;
}


int main(int argc, char** argv) {
	return run_single_tracer(argc, argv);
}